package com.qf.index;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ConcurrentHashSet;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.db.Entity;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Utils {


    public static Integer getStrToInt(Entity entry, String field) {
        Integer str = entry.getInt(field);
        return null == str ? 0 : str;
    }

    public static void main(String[] args) throws SQLException {

        String c1 = "[Butler, Javed] SUNY Stony Brook, Dept Med, Stony Brook, NY USA.;-;" +
                "[Udelson, James E.] Tufts Med Ctr, Div Cardiol, CardioVasc Ctr, Boston, MA USA.;-;" +
                "[O'Connor, Christopher] Inova Heart & Vasc Inst, Div Cardiol, Falls Church, VA USA.;-;" +
                "[Sabbah, Hani N.] Henry Ford Hosp, Dept Med, Div Cardiovasc Med, Detroit, MI USA.;-;" +
                "[Metra, Marco] Univ Brescia, Div Cardiol, Civil Hosp, Brescia, Italy.;-;" +
                "[Shah, Sanjiv J.] NW Univ Feinberg Sch Med, Div Cardiol, Chicago, IL USA.;-;" +
                "[Kitzman, Dalane W.] Univ Calif San Francisco, Cardiovasc Med, Wake Forest Sch Med, Winston Salem, NC USA.;-;" +
                "[Teerlink, John R.] Univ Calif San Francisco, Div Cardiol, San Francisco, CA USA.;-;" +
                "[Bernstein, Harold S.] Merck & Co Inc, Kenilworth, NJ USA.;-;" +
                "[Brooks, Gabriel; Hellawell, Jennifer; Satler, Carol] Gilead Sci, Foster City, CA USA.;-;" +
                "[Depre, Christophe; Kozinn, Marc] Amgen Inc, Thousand Oaks, CA USA.;-;" +
                "[DeSouza, Mary M.; Donovan, Mark; Frost, Robert J.; Mugnier, Pierrre; Mushonga, Sekayi; Russo, Cesare] Bristol Myers Squibb, Princeton, NJ USA.;-;" +
                "[Dinh, Wilfried] Bayer, Wuppertal, Germany.;-;" +
                "[Dinh, Wilfried] Univ Hosp Witten Herdecke Germany, Dept Cardiol, HELIOS Clin Wuppertal, Wuppertal, Germany.;-;" +
                "[Donovan, Mark] AstraZeneca, Gaithersburg, MD USA.;-;" +
                "[Frische-Danielson, Regina; Hellawell, Jennifer] AstraZeneca, Gothenburg, Sweden.;-;" +
                "[Garza, Dahlia; Ishihara, Shiro] Relypsa Inc, Redwood City, CA USA.;-;" +
                "[Kay-Mugford, Patricia] Vifor Pharma, Opfikon, Switzerland.;-;" +
                "[Russo, Cesare] Nippon Med Sch Musashi Kosugi Hosp, Dept Cardiol, Kawasaki, Kanagawa, Japan.;-;" +
                "[Roessig, Lothar] Novartis Pharmaceut Inc, Hanover, NH USA.;-;" +
                "[Salsali, Afshin] Bayer Pharma AG, Wuppertal, Germany.;-;" +
                "[Larson, Christopher J.] Columbia Univ, Med Ctr, New York, NY USA.;-;" +
                "[Salsali, Afshin; Ticho, Barry] Pharmaceut Inc, Boehringer Ingelheim Pharmaceut Inc, Ridgefield, CT USA.;-;" +
                "[Stockbridge, Norman] Moderna Therapeut, Feinberg Sch Med, Cambridge, MA USA.;-;" +
                "[Gheorghiade, Mihai] United States Food & Drug Adm, Div Cardiovasc & Renal Prod, Silver Spring, MD USA.;-;" +
                "[Stockbridge, Norman; Gheorghiade, Mihai] SUNY Stony Brook, Div Cardiol, Room 080, Stony Brook, NY 11794 USA.";
        List<En.C1Auth> c1Auth = getC1List(c1);
        for (En.C1Auth auth : c1Auth) {
            System.out.println(auth);
        }

    }

    public static List<En.C1Auth> getRpListSort(String rps, boolean isOrder, Set<String> pinYinSet) {
        // 获取RP作者信息，2025.01.14版本
        List<En.C1Auth> list = new ArrayList<>();
        if (null != rps && rps.length() > 5) {
            String[] split = rps.replace(" (通讯作者)，", "ĺļ").replace("(reprint author),", "ĺļ").replace(" (corresponding author), ", "ĺļ").replaceAll(" +", " ").split("\\.; ");
            for (int i = 0; i < split.length; i++) {
                String[] split1 = split[i].split("ĺļ");
                if (split1.length == 2) {
                    En.C1Auth c1Auth = new En.C1Auth();
                    List<String> authList = Utils.c1AuthSort(Utils.addAuthList(split1[0]), isOrder, pinYinSet);
                    c1Auth.setAuthList(authList);
                    String info = Utils.dellEnd(split1[1]);
                    c1Auth.setInfo(info);
                    String full = cList.reArgs(info);
                    c1Auth.setFull(full);
                    c1Auth.setSort(i + 1);
                    list.add(c1Auth);
                }
            }
        }
        return list;
    }

    public static List<En.C1Auth> getPmC1ListSort(String authorExtend, Set<String> pinYinSet) {
        // PubMed的作者及单位信息保存成与WOS的相一致，PubMed原始的格式是，如果同一个作者有多个单位(38774450)，那么作者是一条，单位是集合，而WOS的格式则是有多条。其二，如果同一单位有多个作者(29361890)，PubMed是多条记录，而WOS会将各作者合并，共同对应一个单位，即只有一条记录。现统一采用WOS的格式返回。增加了作者排序。
        Map<String, List<String>> infoAndAuthListMap = new LinkedHashMap<>();
        if (null != authorExtend && authorExtend.contains("Affiliation")) {
            JSONArray objects = JSONUtil.parseArray(authorExtend);
            Set<String> authorSet = new HashSet<>();
            for (Object object : objects) {
                JSON parse = JSONUtil.parse(object);
                String lastName = null != parse.getByPath("LastName") ? parse.getByPath("LastName").toString().trim() : "";
                String foreName = null != parse.getByPath("ForeName") ? parse.getByPath("ForeName").toString().trim() : "";
                if ("-".equals(foreName)) {
                    authorSet.add(lastName);
                } else {
                    authorSet.add(lastName + ", " + foreName);
                }
            }
            // Chang-Ming Huang -> isOrder = ture 统一使用：Huang ChangMing
            boolean isOrder = BasisTable.getPiYiOrder(CollUtil.join(authorSet, "; "), pinYinSet);
            for (Object object : objects) {
                JSON parse = JSONUtil.parse(object);
                String lastName = null != parse.getByPath("LastName") ? parse.getByPath("LastName").toString().trim() : "";
                String foreName = null != parse.getByPath("ForeName") ? parse.getByPath("ForeName").toString().trim() : "";
                String author = "-".equals(foreName) ? lastName.trim() : (lastName + " " + foreName.replace("  ", " ").trim());
                if (isOrder && author.lastIndexOf(" ") > 0) {
                    String end = author.substring(0, author.lastIndexOf(" "));
                    String start = author.substring(author.lastIndexOf(" "));
                    // 部分英文作者的名称，可能有单个字母的，不给予转换。
                    if (end.length() > 1 && start.length() > 1) {
                        author = start + " " + end;
                    }
                }
                author = Utils.addComma(author);
                if (null != parse.getByPath("Affiliation")) {
                    String affiliation = parse.getByPath("Affiliation").toString();
                    List<String> infoList = com.alibaba.fastjson.JSON.parseArray(affiliation, String.class);
                    if (infoList.isEmpty()) {
                        // 如果该作者没有单位，也先给加进去，后续会针对没有单位信息的作者进行处理。
                        Utils.putStrListStr(infoAndAuthListMap, "", author);
                    } else {
                        for (String info : infoList) {
                            Utils.putStrListStr(infoAndAuthListMap, Utils.dellEnd(info), author);
                        }
                    }
                }
            }
        }
        List<En.C1Auth> list = new ArrayList<>();
        for (Map.Entry<String, List<String>> entry : infoAndAuthListMap.entrySet()) {
            String info = entry.getKey();
            if (info.isEmpty()) {
                // 对于没有单位信息的作者，也给加进去。
                List<String> auths = entry.getValue();
                for (String auth : auths) {
                    En.C1Auth c1Auth = new En.C1Auth();
                    List<String> authList = new ArrayList<>();
                    authList.add(auth);
                    c1Auth.setAuthList(authList);
                    c1Auth.setSort(list.size() + 1);
                    list.add(c1Auth);
                }
            } else {
                En.C1Auth c1Auth = new En.C1Auth();
                c1Auth.setAuthList(entry.getValue());
                c1Auth.setInfo(Utils.modifyEmail(info));
                c1Auth.setSort(list.size() + 1);
                list.add(c1Auth);
            }
        }
        return list;
    }

    public static boolean getC1ListSort(String c1s, Set<String> pinYinSet, List<En.C1Auth> c1List) {
        // 获取C1的单位信息，如果存在重复，给予删除，且仍保留原来的顺序。2024.12.11发现有超长的unit信息，即同一个作者在多个单位，WOS:000978358600001，现已改进。获取C1作者信息，2025.01.14版本。.replace(".;-;", "; ")(样例：000397583000001)，增加了作者排序。
        Set<String> authSet = getC1AllAuthSet(c1s);
        boolean isOrder = BasisTable.getPiYiOrder(CollUtil.join(authSet, "; "), pinYinSet);
        if (null != c1s && c1s.length() > 6) {
            c1s = c1s.replace("；", "; ").replace(".;-;", "; ").replaceAll(" +", " ").trim();
            String[] split = (c1s.contains("; [") || c1s.contains("] ")) ? c1s.split("; \\[") : c1s.split("; ");
            for (int i = 0; i < split.length; i++) {
                String c1 = split[i].trim();
                String units = c1.indexOf("] ") > 0 ? c1.substring(c1.indexOf("] ") + "] ".length()).trim() : c1;
                units = units.endsWith(".") ? units.substring(0, units.length() - ".".length()).trim() : units.trim();
                List<String> authList = c1AuthSort(addAuthList(c1.equals(units) ? "" : c1), isOrder, pinYinSet);
                if (units.contains("; ")) {
                    String[] unitList = units.split("; ");
                    for (String unit : unitList) {
                        En.C1Auth c1Auth = new En.C1Auth();
                        c1Auth.setAuthList(authList);
                        String info = Utils.dellEnd(unit);
                        c1Auth.setInfo(info);
                        String full = cList.reArgs(info);
                        c1Auth.setFull(full);
                        // 为什么加1？这里是作者的序号，计算机中是从0开始，普通用户一般认为是从1开始，所以统一加1。
                        c1Auth.setSort(i + 1);
                        c1List.add(c1Auth);
                    }
                } else {
                    En.C1Auth c1Auth = new En.C1Auth();
                    c1Auth.setAuthList(authList);
                    String info = Utils.dellEnd(units);
                    c1Auth.setInfo(info);
                    String full = cList.reArgs(info);
                    c1Auth.setFull(full);
                    c1Auth.setSort(i + 1);
                    c1List.add(c1Auth);
                }
            }
        }
        return isOrder;
    }

    public static List<String> c1AuthSort(List<String> authList, boolean isOrder, Set<String> pinYinSet) {
        List<String> sortList = new ArrayList<>();
        for (String original : authList) {
            String authos = original;
            if (isOrder && original.lastIndexOf(" ") > 0) {
                String end = original.substring(0, original.lastIndexOf(" "));
                String start = original.substring(original.lastIndexOf(" "));
                // 部分英文作者的名称，可能有单个字母的，不给予转换。
                if (end.length() > 1 && start.length() > 1) {
                    authos = start + " " + end;
                }
            }
            String auth = BasisTable.changeAuthOrder(authos, pinYinSet);
            sortList.add(addComma(auth));
        }
        return sortList;
    }

    public static String addComma(String auth) {
        // 对没有逗号的作者，添加上逗号。
        if (auth.length() > 3) {
            if (auth.contains(" ") && !auth.contains(",")) {
                int i = auth.indexOf(" ");
                String r = auth.substring(0, i) + ", " + auth.substring(i);
                return Utils.dellEnd(r.replaceAll(" +", " "));
            } else {
                return Utils.dellEnd(auth);
            }
        }
        return "";
    }

    public static List<String> addAuthList(String c1) {
        String auths = c1.indexOf("] ") > 0 ? c1.substring(0, c1.indexOf("] ") + "] ".length()).trim() : c1.trim();
        auths = auths.endsWith("]") ? auths.substring(0, auths.length() - "]".length()).trim() : auths.trim();
        auths = auths.startsWith("[") ? auths.substring("[".length()).trim() : auths.trim();
        auths = auths.endsWith(".") ? auths.substring(0, auths.length() - ".".length()).trim() : auths.trim();
        String[] split = auths.split("; ");
        List<String> list = new ArrayList<>();
        for (String auth : split) {
            list.add(Utils.dellEnd(auth));
        }
        return list;
    }

    public static Set<String> getC1AllAuthSet(String c1s) {
        Set<String> authSet = new HashSet<>();
        if (null != c1s && c1s.length() > 6) {
            c1s = c1s.replace("；", "; ").replace(".;-;", "; ").replaceAll(" +", " ").trim();
            String[] split = (c1s.contains("; [") || c1s.contains("] ")) ? c1s.split("; \\[") : c1s.split("; ");
            for (String c1 : split) {
                String units = c1.indexOf("] ") > 0 ? c1.substring(c1.indexOf("] ") + "] ".length()).trim() : c1.trim();
                authSet.addAll(addAuthList(c1.equals(units) ? "" : c1));
            }
        }
        return authSet;
    }

    public static List<String> getFieldList(Entity entry, String field) {
        String str = entry.getStr(field);
        if (null != str && str.contains("\"]")) {
            return JSONObject.parseArray(str, String.class);
        }
        return new ArrayList<>();
    }

    public static List<String> getStrToList(Entity entry, String field) {
        String str = entry.getStr(field);
        if (null != str && !str.isEmpty()) {
            return new ArrayList<>(Arrays.asList(str.split("; ")));
        }
        return new ArrayList<>();
    }

    public static String toTitleCase(String input) {
        // 将期刊名称的首字母转大写，后面的转小写，比如："wake forest univ，'bowman (gray sch) med. dept pathol;winston-salem,nc 27103" -> "Wake Forest Univ，'Bowman (Gray Sch) Med. Dept Pathol;Winston-Salem,Nc 27103"
        if (null == input || input.trim().isEmpty()) {
            return "";
        }
        if (NumberUtil.isInteger(input)) {
            return input;
        }
        StringBuilder titleCase = new StringBuilder();
        boolean nextTitleCase = true;
        for (char c : input.toCharArray()) {
            if (Character.isSpaceChar(c) || c == ';' || c == ',' || c == '，' || c == '?' || c == '!' || c == ':' || c == '"' || c == '\\' || c == '/' || c == '-' || c == '；' || c == '&' || c == '(' || c == ')' || c == '<' || c == '>' || c == '\'') {
                nextTitleCase = true;
                titleCase.append(c);
            } else if (nextTitleCase) {
                titleCase.append(Character.toTitleCase(c));
                nextTitleCase = false;
            } else {
                titleCase.append(Character.toLowerCase(c));
            }
        }
        // 通常英文的介词是小写，而不是大写，所以需要把常见的介词给转换成小写。
        return titleCase.toString().replace(" About ", " about ").replace(" Above ", " above ").replace(" Across ", " across ").replace(" After ", " after ")
                .replace(" Against ", " against ").replace(" Along ", " along ").replace(" Among ", " among ").replace(" Around ", " around ")
                .replace(" As ", " as ").replace(" At ", " at ").replace(" Before ", " before ").replace(" Behind ", " behind ").replace(" Below ", " below ")
                .replace(" Beneath ", " beneath ").replace(" Beside ", " beside ").replace(" Between ", " between ").replace(" By ", " by ")
                .replace(" Down ", " down ").replace(" During ", " during ").replace(" Except ", " except ").replace(" For ", " for ").replace(" From ", " from ")
                .replace(" In ", " in ").replace(" Inside ", " inside ").replace(" Into ", " into ").replace(" Like ", " like ").replace(" Near ", " near ")
                .replace(" Of ", " of ").replace(" Off ", " off ").replace(" On ", " on ").replace(" Onto ", " onto ").replace(" Out ", " out ")
                .replace(" Outside ", " outside ").replace(" Over ", " over ").replace(" Past ", " past ").replace(" Since ", " since ").replace("'S ", "'s ")
                .replace(" Through ", " through ").replace(" Throughout ", " throughout ").replace(" To ", " to ").replace(" Toward ", " toward ")
                .replace(" Under ", " under ").replace(" Underneath ", " underneath ").replace(" Until ", " until ").replace(" Up ", " up ")
                .replace(" Upon ", " upon ").replace(" With ", " with ").replace(" Within ", " within ").replace(" Without ", " without ");
    }

    public static List<En.C1Auth> getWfC1List(String auorg) {
        // 万方的C1，转换为WOS格式，使用auorg字段。万方的也有中文和英文的单位信息。万方的数据，好多有医院但没有科室信息。
        List<En.C1Auth> list = new ArrayList<>();
        if (null != auorg && auorg.length() > 1) {
            // 王恕立:武汉理工大学; 滕泽伟:湖北经济学院经济与贸易学院,湖北武汉,430205; 王许亮:武汉理工大学
            Map<String, List<String>> infoAndAuthListMap = new LinkedHashMap<>();
            String[] split = auorg.split("; ");
            for (String auths : split) {
                String[] authUnits = auths.split(":");
                String auth = "";
                for (int i = 0; i < authUnits.length; i++) {
                    String s = authUnits[i].replaceAll(" +", " ").replace("（", "(").replace("）", ")").replace("【", "[").replace("】", "]")
                            .replace("( ", "(").replace(" )", ")").replace("[ ", "[").replace(" ]", "]").replace("()", "").replace("[]", "").trim();
                    if (i == 0 && (s.length() < 6 || s.contains("·") || s.contains("(") && s.contains(")") || s.contains("[") && s.contains("]") || !isContainChinese(s) && s.length() < 26)) {
                        auth = s;
                    } else if (auth.length() > 1) {
                        String[] units = s.split(";");
                        for (String unit : units) {
                            putStrListStr(infoAndAuthListMap, dellEnd(unit), auth);
                        }
                    }
                }
            }
            for (Map.Entry<String, List<String>> entry : infoAndAuthListMap.entrySet()) {
                En.C1Auth c1Auth = new En.C1Auth();
                c1Auth.setAuthList(entry.getValue());
                String info = entry.getKey();
                c1Auth.setInfo(info);
                c1Auth.setSort(list.size() + 1);
                list.add(c1Auth);
            }
        }
        return list;
    }

    public static List<En.C1Auth> getZhC1List(String artorgannames) {
        // 中华的C1，转换为WOS格式，使用artorgannames字段。中华的数据作者与单位无法对应，因此，作者的信息全部给予放弃。中华的也有中文和英文的单位信息。中华的作者可以使用"authornames"字段。原来的sort_auth好多没有值，且全部没有科室信息，本次的有科室信息，但无作者。
        List<En.C1Auth> list = new ArrayList<>();
        if (null != artorgannames && artorgannames.contains("\"]")) {
            List<String> authUnits = JSONObject.parseArray(artorgannames, String.class);
            for (String unit : authUnits) {
                En.C1Auth c1Auth = new En.C1Auth();
                c1Auth.setInfo(dellEnd(unit));
                c1Auth.setSort(list.size() + 1);
                list.add(c1Auth);
            }
        }
        return list;
    }

    public static List<En.C1Auth> getWpC1List(String origin) {
        // 维普的C1，转换为WOS格式，使用origin字段。维普的数据很不规范，有"; "分割的，也有".; "分割的，两种也都不准确，而且作者与单位完全无法对应的上，因此，作者的信息全部给予放弃。维普的也有中文和英文的单位信息。
        List<En.C1Auth> list = new ArrayList<>();
        if (null != origin && origin.length() > 3) {
            String[] split = origin.split("; ");
            for (String info : split) {
                En.C1Auth c1Auth = new En.C1Auth();
                c1Auth.setInfo(dellEnd(info));
                c1Auth.setSort(list.size() + 1);
                list.add(c1Auth);
            }
        }
        return list;
    }

    public static List<En.C1Auth> getZwC1List(String auemorg) {
        // 知网的C1，转换为WOS格式，使用auemorg字段。原本知网是有邮箱的，转换后邮箱信息就丢失了。知网的也有中文和英文的单位信息。
        List<En.C1Auth> list = new ArrayList<>();
        if (null != auemorg && auemorg.contains("}]")) {
            Map<String, List<String>> infoAndAuthListMap = new LinkedHashMap<>();
            List<En.AuEmOrg> auEmOrgList = JSONObject.parseArray(auemorg, En.AuEmOrg.class);
            for (En.AuEmOrg emOrg : auEmOrgList) {
                List<String> infoList = new ArrayList<>();
                List<String> orgList = emOrg.getOrg();
                // 为什么不直接CollUtil.join(orgList, "; ")？原因是担心orgList内有"; "字符，后续以"; "切割时会导致数据混乱，所以，提前给予转换成", "。
                for (String org : orgList) {
                    infoList.add(org.replace("; ", ", "));
                }
                putStrListStr(infoAndAuthListMap, dellEnd(CollUtil.join(infoList, "; ")), emOrg.getAu());
            }
            for (Map.Entry<String, List<String>> entry : infoAndAuthListMap.entrySet()) {
                En.C1Auth c1Auth = new En.C1Auth();
                c1Auth.setAuthList(entry.getValue());
                String info = entry.getKey().replaceAll("target=\".*?\">", "").trim();
                c1Auth.setInfo(info);
                c1Auth.setSort(list.size() + 1);
                list.add(c1Auth);
            }
        }
        return list;
    }

    public static List<String> getCsCdUnitList(String info) {
        // 从CSCD的中文的单位信息中，提取出中文的大学名称和医院名称，本方法不能提取英文的，英文的需要使用英文的方法。注意，英文的判断不能是不包含中文，因为CSCD的数据中有好多类似："University Laval, 加拿大"，所以，应当使用包含诸如：the of univ hosp之类。
        List<String> univList = cList.getCnUnivList();
        List<String> hospList = cList.getCnHospList();
        Set<String> errUnitSet = cList.getCnErrUnitSet();
        List<String> ordinaryList = new ArrayList<>();
        // .replaceAll("\\s+(\\d+)", "$1") ->数字前面如果有空格则给予删除  四川大学华西第二医院/华西妇产儿童医院(.replace("/", "|")去掉的原因)
        String[] split = info.replace("|", " ").replace("　", " ").replace("（", "(").replace("）", ")").replaceAll(" +", " ").replace(" (", "(").replace(" )", ")")
                .replace("、", "|").replace("—", "|").replace("!", "|").replace("！", "|").replace("暨", "|")
                .replace("癌症中心·", "癌症中心|").replace("办公室-", "办公室|").replace("保健院·", "保健院|").replace("单位-", "单位|").replace("防治中心·", "防治中心|").replace("港航-", "港航|")
                .replace("规划署-", "规划署|").replace("科学院·", "科学院|").replace("气象局-", "气象局|").replace("实验室-", "实验室|").replace("实验室·", "实验室|").replace("系统所-", "系统所|")
                .replace("学部-", "学部|").replace("学系-", "学系|").replace("学院-", "学院|").replace("研究基地-", "研究基地|").replace("研究所-", "研究所|").replace("研究所·", "研究所|")
                .replace("研究中心·", "研究中心|").replace("医学中心·", "医学中心|").replace("医院-", "医院|").replace("医院·", "医院|").replace("政府-", "政府|").replace("中国·", "中国|")
                .replace(",", "|").replace(".", "|").replace(";", "|").replace(":", "|").replaceAll("\\s+(\\d+)", "$1").replaceAll("\\d{6}", "|").replaceAll("\\d{5}", "|")
                .replace("，", "|").replace("。", "|").replace("；", "|").replace("：", "|").split("\\|");
        for (String string : split) {
            String s = string.trim();
            if (s.length() > 1) {
                String univ = findUnit(univList, s, errUnitSet);
                String hosp = findUnit(hospList, s, errUnitSet);
                if (!univ.isEmpty() && !hosp.isEmpty()) {
                    ordinaryList.add(univ);
                    ordinaryList.add(hosp);
                } else if (!univ.isEmpty()) {
                    ordinaryList.add(univ);
                } else if (!hosp.isEmpty()) {
                    ordinaryList.add(hosp);
                }
            }
        }
        return ordinaryList;
    }

    public static String findUnit(List<String> unitList, String str, Set<String> errUnitSet) {
        if ("中国科学院".equals(str)) {
            return "中国科学院";
        }
        for (String unit : unitList) {
            if (str.contains(unit) && !str.equals(unit)) {
                // lastIndexOf:四川大学华西第二医院/华西妇产儿童医院
                String trim = str.substring(0, str.lastIndexOf(unit) + unit.length());
                String end = str.substring(trim.length());
                // 中国石油大学(华东)信息与控制工程学院    ||    电网智能化调度与控制教育部重点实验室(山东大学)
                if ((end.startsWith("(") && end.contains(")")) || end.startsWith(")")) {
                    trim += end.substring(0, end.indexOf(")") + ")".length());
                }
                end = str.substring(trim.length());
                // 东部战区总医院临床药学科(南京大学医学院附属金陵医院)
                if (trim.contains("(") && (trim.endsWith("大学") || trim.endsWith("学院")) && end.contains(")")) {
                    trim += end.substring(0, end.indexOf(")") + ")".length());
                }
                trim = trim.startsWith("《") ? trim.substring("《".length()) : trim;
                trim = (errUnitSet.contains(trim.trim()) || trim.trim().length() < 4) ? "" : trim;
                return trim.length() < 255 ? trim.trim() : "";
            }
        }
        return "";
    }

    public static List<En.C1Auth> getCsC1List(String z6) {
        // 提取CSCD的C1信息，请注意，这里使用的字段是Z6而不是C1，原因是Z6比较全，而且Z6是以中文为主。原始的CSCD的Z6数据格式与WOS也是不一样的，在此也给统一成了WOS的格式。
        List<En.C1Auth> list = new ArrayList<>();
        if (null != z6 && z6.length() > 3) {
            Map<String, List<String>> infoAndAuthListMap = new LinkedHashMap<>();
            String str = z6.replace("|", " ").replaceAll(".; ([\u4e00-\u9fa5]{2,8}), ", " |$1, ").replaceAll(" ([0-9]{6}), ([\u4e00-\u9fa5]{2,8}).; ", " $1, $2|")
                    .replaceAll(", ([\u4e00-\u9fa5]{2,8}).; ([\u4e00-\u9fa5]{2,8})", ", $1. |$2").replaceAll(" ([A-Z][a-z]+).;-;([\u4e00-\u9fa5]{2,8}), ", " $1.|$2, ").replaceAll(".;-;([\u4e00-\u9fa5？-]{2,8}), ", ". |$1, ")
                    .replaceAll(", ([0-9]{6}).; ([\u4e00-\u9fa5]{2,6})", ", $1|$2").replaceAll(".; ([\u4e00-\u9fa5]{2,8})·([\u4e00-\u9fa5]{2,8}), ", ". |$1·$2, ")
                    .replaceAll(", ([0-9]{6}).$", " $1|").replaceAll(" ([0-9]{6}).$", " $1|").replaceAll(", (美国|中国|俄罗斯|英国|法国|德国|日本|印度|巴西|加拿大|澳大利亚|韩国|意大利|墨西哥|南非|土耳其|沙特阿拉伯|阿根廷|印度尼西亚|伊朗).; ", ", $1|")
                    .replaceAll(" ([\u4e00-\u9fa5]{2,8}).; ([\u4e00-\u9fa5 ？-]{2,8})", ", $1|$2").replaceAll(".;-;([\u4e00-\u9fa5]{2,8})·([\u4e00-\u9fa5]{2,8}), ", ". |$1·$2, ")
                    .replaceAll(".;-;([\u4e00-\u9fa5]{2,4})(\\(供稿\\)|\\(著\\)|\\(翻译\\)|\\(校\\)|\\(编译\\)|\\(译\\)|（译）|\\(综述\\)|\\(审校\\)|\\(摘译\\)|\\(澳\\))", ", |$1$2")
                    .replaceAll(".; ([\u4e00-\u9fa5]{2,4})(\\(供稿\\)|\\(著\\)|\\(翻译\\)|\\(校\\)|\\(编译\\)|\\(译\\)|（译）|\\(综述\\)|\\(审校\\)|\\(摘译\\)|\\(澳\\))", ", |$1$2").trim();
            String[] split = str.split("\\|");
            for (String s : split) {
                if (s.contains(", ")) {
                    String auth = s.substring(0, s.indexOf(", ")).trim();
                    String info = s.substring(s.indexOf(", ") + ", ".length()).trim();
                    auth = auth.endsWith("-") ? auth.substring(0, auth.length() - "-".length()).trim() : auth;
                    putStrListStr(infoAndAuthListMap, dellEnd(info), auth);
                }
            }
            for (Map.Entry<String, List<String>> entry : infoAndAuthListMap.entrySet()) {
                En.C1Auth c1Auth = new En.C1Auth();
                c1Auth.setAuthList(entry.getValue());
                String info = entry.getKey();
                c1Auth.setInfo(info);
                c1Auth.setSort(list.size() + 1);
                list.add(c1Auth);
            }
        }
        return list;
    }


    public static void putStrSetStr(String name, String str, Map<String, Set<String>> strSetStrMap) {
        strSetStrMap.compute(name.trim(), (k, v) -> {
            if (v == null) {
                v = new ConcurrentHashSet<>();
            }
            v.add(str.trim());
            return v;
        });
    }

    public static void putStrIntSetInt(Map<String, Map<Integer, Set<Integer>>> unitAuthAidMap, String mail, int auth_id, int aId) {
        if (!mail.isEmpty() && auth_id > 0 && aId > 0) {
            unitAuthAidMap.compute(mail, (k, v) -> {
                if (v == null) {
                    v = new ConcurrentHashMap<>();
                }
                v.compute(auth_id, (k1, v1) -> {
                    if (v1 == null) {
                        v1 = new ConcurrentHashSet<>();
                    }
                    v1.add(aId);
                    return v1;
                });
                return v;
            });
        }
    }

    public static void putStrStrIntSetMap(Map<String, Map<String, Set<Integer>>> coAuthMap, String auth, String unit, int aId) {
        coAuthMap.compute(auth, (k, v) -> {
            if (v == null) {
                v = new ConcurrentHashMap<>();
            }
            v.compute(unit, (k1, v1) -> {
                if (v1 == null) {
                    v1 = new ConcurrentHashSet<>();
                }
                v1.add(aId);
                return v1;
            });
            return v;
        });
    }

    // 插入这类的Map：主键为String，值为Map，且为Integer类型，一般用于保存某个类型（比如基金或单位）下的某个名称总共出现的次数。
    public static void putStrStrInt(String type, String name, Map<String, Map<String, Integer>> strStrIntMap) {
        strStrIntMap.compute(type, (k, v) -> {
            if (v == null) {
                v = new ConcurrentHashMap<>();
            }
            v.compute(name, (key, value) -> value != null ? (value + 1) : 1);
            return v;
        });
    }

    public static List<En.C1Auth> getRpList(String rps) {
        // 获取RP作者信息，2025.01.14版本
        List<En.C1Auth> list = new ArrayList<>();
        if (null != rps && rps.length() > 5) {
            String[] split = rps.replace(" (通讯作者)，", "ĺļ").replace("(reprint author),", "ĺļ").replace(" (corresponding author), ", "ĺļ").replaceAll(" +", " ").split("\\.; ");
            for (int i = 0; i < split.length; i++) {
                String[] split1 = split[i].split("ĺļ");
                if (split1.length == 2) {
                    En.C1Auth c1Auth = new En.C1Auth();
                    c1Auth.setAuthList(authsToList(split1[0]));
                    String info = dellEnd(split1[1]);
                    c1Auth.setInfo(info);
                    String full = cList.reArgs(info);
                    c1Auth.setFull(full);
                    c1Auth.setSort(i + 1);
                    list.add(c1Auth);
                }
            }
        }
        return list;
    }

    public static List<En.C1Auth> getPmC1List(String authorExtend) {
        // PubMed的作者及单位信息保存成与WOS的相一致，PubMed原始的格式是，如果同一个作者有多个单位(38774450)，那么作者是一条，单位是集合，而WOS的格式则是有多条。其二，如果同一单位有多个作者(29361890)，PubMed是多条记录，而WOS会将各作者合并，共同对应一个单位，即只有一条记录。现统一采用WOS的格式返回。
        Map<String, List<String>> infoAndAuthListMap = new LinkedHashMap<>();
        if (null != authorExtend && authorExtend.contains("Affiliation")) {
            JSONArray objects = JSONUtil.parseArray(authorExtend);
            for (Object object : objects) {
                JSON parse = JSONUtil.parse(object);
                String lastName = null != parse.getByPath("LastName") ? parse.getByPath("LastName").toString().trim() : "";
                String foreName = null != parse.getByPath("ForeName") ? parse.getByPath("ForeName").toString().trim() : "";
                String author = "-".equals(foreName) ? lastName.trim() : (lastName + " " + foreName.replace("  ", " ").trim());
                if (null != parse.getByPath("Affiliation")) {
                    String affiliation = parse.getByPath("Affiliation").toString();
                    List<String> infoList = com.alibaba.fastjson.JSON.parseArray(affiliation, String.class);
                    for (String info : infoList) {
                        Utils.putStrListStr(infoAndAuthListMap, dellEnd(info), author);
                    }
                }
            }
        }
        List<En.C1Auth> list = new ArrayList<>();
        for (Map.Entry<String, List<String>> entry : infoAndAuthListMap.entrySet()) {
            En.C1Auth c1Auth = new En.C1Auth();
            c1Auth.setAuthList(entry.getValue());
            String info = entry.getKey();
            c1Auth.setInfo(modifyEmail(info));
            c1Auth.setSort(list.size() + 1);
            list.add(c1Auth);
        }
        return list;
    }

    /**
     * 如果含有.edu的邮箱，则把域名提取出来，并添加到邮箱的前面，如果不含.edu的邮箱，则仅在邮箱前面加上逗号。.replace("$", "Łł")？因为$是特殊字符，需要提前作下处理，而^没有发现有此现象。
     * School of BioSciences g.shaw@unimelb.EDu.CN -> School of BioSciences [ĺunimelb.EDu.CNļ], g.shaw@unimelb.EDu.CN 如果是edu.cn或其它的含有edu的也在邮箱前面添加域名
     * School of BioSciences g.shaw@unimelb.EDu BioSciences -> School of BioSciences [ĺunimelb.EDuļ], g.shaw@unimelb.EDu, BioSciences 如果是edu的邮箱，则在邮箱前面添加域名
     * School of BioSciences g.shaw@unimelb.E2Du BioSciences -> School of BioSciences , g.shaw@unimelb.E2Du, BioSciences 如果是非edu的邮箱，则在前后加逗号
     * School of BioSciences g.shaw@unimelb.E2Du BioSciences g.shaw@unimelb.EDu -> School of BioSciences g.shaw@unimelb.E2Du BioSciences [ĺunimelb.EDuļ], g.shaw@unimelb.EDu 如果存在两个邮箱，则只关注edu的邮箱
     */
    public static String modifyEmail(String input) {
        // 在PubMed的作者单位信息中，往往会包含作者的edu邮箱，利用此邮箱可以区分作者的单位，在程序给予了提取，比如："[ĺyalE.eduļ], "，邮箱后缀加方括号，括号内以"ĺ"开头，以"ļ"结尾，方便识别。
        try {
            input = input.replace("$", "Łł");
            Pattern pattern = Pattern.compile("(\\S+)@(\\S+\\.(?:edu|eDu|edU|EDu|EdU|eDU|EDU|Edu)(?:\\.\\S+)?(?: |$))");
            Matcher matcher = pattern.matcher(input);
            StringBuffer result = new StringBuffer();
            while (matcher.find()) {
                String emailPrefix = matcher.group(1);
                String domainPart = matcher.group(2).trim();
                String replacement = "[ĺ" + domainPart + "ļ], " + emailPrefix + "@" + domainPart + ", ";
                matcher.appendReplacement(result, replacement);
            }
            if (result.length() == 0) {
                return input.replaceAll("(\\S+@\\S+\\.\\S+)", ", $1, ").replaceAll(" +", " ").replaceAll(", $", "").replaceAll("^, ", "").replace("Łł", "$").trim();
            } else {
                matcher.appendTail(result);
                if (result.length() == 0) {
                    return input.replaceAll("(\\S+@\\S+\\.\\S+)", ", $1, ").replaceAll(", $", "").replaceAll("^, ", "").replace("Łł", "$").trim();
                } else {
                    return result.toString().replaceAll(", $", "").replaceAll("^, ", "").replace("Łł", "$").trim();
                }
            }
        } catch (Exception e) {
//            System.out.println("正则出问题了：" + input);
            return input.replace("Łł", "$");
        }
    }

    public static List<En.C1Auth> getC1List(String c1s) {
        // 获取C1的单位信息，如果存在重复，给予删除，且仍保留原来的顺序。2024.12.11发现有超长的unit信息，即同一个作者在多个单位，WOS:000978358600001，现已改进。获取C1作者信息，2025.01.14版本。.replace(".;-;", "; ")(样例：000397583000001)
        List<En.C1Auth> list = new ArrayList<>();
        if (null != c1s && c1s.length() > 6) {
            c1s = c1s.replace("；", "; ").replace(".;-;", "; ").replaceAll(" +", " ").trim();
            String[] split = (c1s.contains("; [") || c1s.contains("] ")) ? c1s.split("; \\[") : c1s.split("; ");
            for (int i = 0; i < split.length; i++) {
                String c1 = split[i];
                String units = c1.indexOf("] ") > 0 ? c1.substring(c1.indexOf("] ") + "] ".length()).trim() : c1.trim();
                units = units.endsWith(".") ? units.substring(0, units.length() - ".".length()).trim() : units.trim();
                String auths = c1.indexOf("] ") > 0 ? c1.substring(0, c1.indexOf("] ") + "] ".length()).trim() : c1.trim();
                List<String> authList = authsToList(c1.trim().equals(units) ? "" : auths);
                if (units.contains("; ")) {
                    String[] unitList = units.split("; ");
                    for (String unit : unitList) {
                        En.C1Auth c1Auth = new En.C1Auth();
                        c1Auth.setAuthList(authList);
                        String info = dellEnd(unit);
                        c1Auth.setInfo(info);
                        String full = cList.reArgs(info);
                        c1Auth.setFull(full);
                        // 为什么加1？这里是作者的序号，计算机中是从0开始，普通用户一般认为是从1开始，所以统一加1。
                        c1Auth.setSort(i + 1);
                        list.add(c1Auth);
                    }
                } else {
                    En.C1Auth c1Auth = new En.C1Auth();
                    c1Auth.setAuthList(authList);
                    String info = dellEnd(units);
                    c1Auth.setInfo(info);
                    String full = cList.reArgs(info);
                    c1Auth.setFull(full);
                    c1Auth.setSort(i + 1);
                    list.add(c1Auth);
                }
            }
        }
        return list;
    }

    public static String dellEnd(String s) {
        s = s.trim();
        s = s.startsWith(",") ? s.substring(1).trim() : s;
        return s.endsWith(".") ? s.substring(0, s.length() - 1).trim() : s;
    }

    public static List<String> authsToList(String auths) {
        // 将作者的字符串，转换为list，如果作者只有空格而没有逗号，则添加上逗号，2025.01.14版本。
        auths = auths.endsWith("]") ? auths.substring(0, auths.length() - "]".length()).trim() : auths.trim();
        auths = auths.startsWith("[") ? auths.substring("[".length()).trim() : auths.trim();
        auths = auths.endsWith(".") ? auths.substring(0, auths.length() - ".".length()).trim() : auths.trim();
        String[] split = auths.split("; ");
        List<String> list = new ArrayList<>();
        for (String auth : split) {
            auth = dellEnd(auth);
            if (auth.length() > 3) {
                if (auth.contains(" ") && !auth.contains(",")) {
                    int i = auth.indexOf(" ");
                    String r = auth.substring(0, i) + ", " + auth.substring(i);
                    list.add(r.replaceAll(" +", " ").trim());
                } else {
                    list.add(auth.trim());
                }
            }
        }
        return list;
    }

    public static String printMemoryStr() {
        try {
            String freeMemory = Runtime.getRuntime().freeMemory() / 1024 / 1024 / 1024 + " Gb";
            String totalMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024 / 1024 + " Gb";
            String maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024 / 1024 + " Gb";
            return " 内存申请：" + maxMemory + " 空余：" + freeMemory + " 获取：" + totalMemory;
        } catch (Exception e) {
            return "";
        }
    }

    public static void putStrAndPmIdSetMap(boolean isModified, Map<String, String> strAndStrMap, String str, int pmId, Map<String, Set<Integer>> strAndPmIdSetMap) {
        if (isModified) {
            putStrSetInt(strAndStrMap.getOrDefault(str, str), pmId, strAndPmIdSetMap);
        } else {
            putStrSetInt(str, pmId, strAndPmIdSetMap);
            strAndStrMap.put(str, str);
        }
    }

    public static void putAuthFenQuYearScoreMap(Map<String, Map<Integer, Double>> authFenQuYearScoreMap, String auth, int year, double score) {
        if (auth.length() > 3 && year > 0 && score > 0.0) {
            authFenQuYearScoreMap.compute(auth, (k, v) -> {
                if (v == null) {
                    v = new ConcurrentHashMap<>();
                }
                v.compute(year, (key, value) -> value != null ? (value + score) : score);
                return v;
            });
        }
    }

    public static Set<String> getGuidelineSet(Set<String> guidelineSet) {
        Set<String> set = new HashSet<>();
        for (String s : guidelineSet) {
            set.add(s.toLowerCase().trim());
        }
        return set;
    }

    public static void putAuthYearPmIdSetMap(Map<String, Map<Integer, Set<Integer>>> authYearPmIdSetMap, String auth, int year, int pmId) {
        if (auth.length() > 3 && year > 0 && pmId > 0) {
            authYearPmIdSetMap.compute(auth, (k, v) -> {
                if (v == null) {
                    v = new ConcurrentHashMap<>();
                }
                v.compute(year, (k1, v1) -> {
                    if (v1 == null) {
                        v1 = new ConcurrentHashSet<>();
                    }
                    v1.add(pmId);
                    return v1;
                });
                return v;
            });
        }
    }

    // 对Map的值进行排序，此排序为最大的在前面，输入一个map，再返回一个排序后的新map，String, Integer类型。
    public static Map<String, Integer> mapSortValueStrInt(Map<String, Integer> mapName) {
        Map<String, Integer> result = new LinkedHashMap<>();
        mapName.entrySet().stream()
                .sorted(Map.Entry.<String, Integer>comparingByValue()
                        .reversed()).forEachOrdered(e -> result.put(e.getKey(), e.getValue()));
        return result;
    }

    // 对Map的值进行排序，此排序为最大的在前面，输入一个map，再返回一个排序后的新map，String, Integer类型，本排序增加了过滤功能，可以设置要前多少（len）个。
    public static Map<String, Integer> mapSortValueIntLen(Map<String, Integer> mapName, Integer len) {
        Map<String, Integer> result = new LinkedHashMap<>();
        mapName.entrySet().stream()
                .sorted(Map.Entry.<String, Integer>comparingByValue()
                        .reversed()).limit(len).forEachOrdered(e -> result.put(e.getKey(), e.getValue()));
        return result;
    }


    public static int getIntField(Entity entry, String field) {
        // 返回Integer类型的字段的值，field为字段的名称。
        if (null != entry) {
            Integer num = entry.getInt(field);
            return (null != num) ? num : 0;
        }
        return 0;
    }


    public static String getStrField(Entity entry, String field) {
        // 返回String类型的字段的值，field为字段的名称。因存在不可见的字符，所以返回时必须.trim()。比如，CSCD表中CSCD:5903288此UT号即存在不可见的字符。
        if (null != entry) {
            String str = entry.getStr(field);
            return (null != str) ? str.trim() : "";
        }
        return "";
    }

    public static String reKw(Map<String, String> reKwMap, String str) {
        String[] split = str.toLowerCase().split("");
        StringBuilder result = new StringBuilder();
        for (String s : split) {
            result.append(reKwMap.getOrDefault(s, s));
        }
        return result.toString();
    }

    // 返回Double类型的字段的值，field为字段的名称。
    public static double getDouField(Entity entry, String field) {
        if (null != entry) {
            Double dou = entry.getDouble(field);
            return (null != dou) ? dou : 0.0;
        }
        return 0.0;
    }

    // 获取现在年，方法是当前时间戳减去6个月后再取年，即2021年7月1日是2021年，2021年6月1日是2020年。此用于新词发现，最长的比较就是一年半与之前的所有相比。
    public static Integer getNowYear() {
        long time = System.currentTimeMillis() - 60 * 60 * 24 * 30 * 6 * 1000L;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
        String format = sdf.format(new Date(time));
        if (NumberUtil.isInteger(format)) {
            return Integer.parseInt(format);
        }
        return 1900;
    }


    // 自动判断文件的编码格式，这里要求必须为.txt结尾的文件。
    public static String codeString(String filePath) {
        String charset = "GBK";
        byte[] first3Bytes = new byte[3];
        try {
            boolean checked = false;
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
            bis.mark(0); // 读者注： bis.mark(0);修改为 bis.mark(100);我用过这段代码，需要修改上面标出的地方。
            // Wagsn注：不过暂时使用正常，遂不改之
            int read = bis.read(first3Bytes, 0, 3);
            if (read == -1) {
                bis.close();
                return charset; // 文件编码为 ANSI
            } else if (first3Bytes[0] == (byte) 0xFF && first3Bytes[1] == (byte) 0xFE) {
                charset = "UTF-16LE"; // 文件编码为 Unicode
                checked = true;
            } else if (first3Bytes[0] == (byte) 0xFE && first3Bytes[1] == (byte) 0xFF) {
                charset = "UTF-16BE"; // 文件编码为 Unicode big endian
                checked = true;
            } else if (first3Bytes[0] == (byte) 0xEF && first3Bytes[1] == (byte) 0xBB
                    && first3Bytes[2] == (byte) 0xBF) {
                charset = "UTF-8"; // 文件编码为 UTF-8
                checked = true;
            }
            bis.reset();
            if (!checked) {
                while ((read = bis.read()) != -1) {
                    if (read >= 0xF0) {
                        break;
                    }
                    if (0x80 <= read && read <= 0xBF) { // 单独出现BF以下的，也算是GBK
                        break;
                    }
                    if (0xC0 <= read && read <= 0xDF) {
                        read = bis.read();
                        if (0x80 <= read && read <= 0xBF) { // 双字节 (0xC0 - 0xDF)
                            // (0x80 - 0xBF),也可能在GB编码内
                            continue;
                        } else {
                            break;
                        }
                    } else if (0xE0 <= read && read <= 0xEF) { // 也有可能出错，但是几率较小
                        read = bis.read();
                        if (0x80 <= read && read <= 0xBF) {
                            read = bis.read();
                            if (0x80 <= read && read <= 0xBF) {
                                charset = "UTF-8";
                                break;
                            } else {
                                break;
                            }
                        } else {
                            break;
                        }
                    }
                }
            }
            bis.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //System.out.println("--文件-> [" + path + "] 采用的字符集为: [" + charset + "]");
        return charset;
    }


    /**
     * 各种类型的数据解析：位数的限定，WOS为15位，EJ EB CJ CB 四种 8-9 位的数字，PM 1-8位且小于 50000000 的数字，GG GGSX 为9位数字，除了WOS外，文献ID号均不得以 0 开头，各种类型的员工代码只能为一个字母，不能是数字 。
     * PM 1 ; UT 2 ; GG 3 ; EB 4 ; EJ 5 ; CB 6 ; CJ 7
     */
    public static String getFileId(String str) {
        if (null == str) {
            return "";
        }
        str = str.replaceAll("\\[+", "[").replaceAll("]+", "]");
        // https://utswmed-ir.tdl.org/bitstream/handle/2152.5/5772/PWS%20Paper%20Supplemental%20Tables[2].pdf?sequence=1|Oligonucleotide Primer Sequences Source ir tdl org SO 2018[GGT915422624].pdf
        Matcher matcher = Pattern.compile("\\[([0-9]+)\\]").matcher(str);
        if (matcher.find()) {
            str = str.replace(matcher.group(0), "");
        }
        str = str.replaceAll(" +", "").toUpperCase().replace("[PMID[PMID", "[PMID").replace("].PDF.PDF", "].PDF").replace("].PDF(1).PDF", "].PDF").replace("].PDF(2).PDF", "].PDF").replace("].PDF(3).PDF", "].PDF");
        if (str.endsWith(".PDF")) {

            // 2023.06.06 新增加SCI-HUB的ID号解析。
            String temp = str.endsWith("H.PDF.PDF") ? str.substring(0, str.length() - ".PDF".length()) : str;
            if (temp.endsWith("H.PDF") && temp.length() > 13) {
                String end = temp.substring(temp.length() - 14);
                String id = end.substring(1, 9);
                if (end.startsWith("S") && NumberUtil.isInteger(id)) {
                    return "SH" + id;
                }
            }

            int pmStart = str.indexOf("[PMID");
            if (pmStart != -1) {
                int pmEnd = str.toUpperCase().indexOf("].PDF");
                if (pmEnd != -1 && (pmEnd - pmStart) > 6 && NumberUtil.isInteger(str.substring(pmStart + 6, pmEnd))) {
                    int id = Integer.parseInt(str.substring(pmStart + 6, pmEnd));
                    if (id < 50000000 && !str.substring(pmStart + 6).startsWith("0")) {
                        if (str.substring(pmStart + 5, pmStart + 6).matches("[a-zA-Z]+")) {
                            return "PM" + str.substring(pmStart + 6, pmEnd);
                        }
                    }
                }
            }
            int utStart = str.indexOf("[WOS");
            if (utStart != -1) {
                int utEnd = str.toUpperCase().indexOf("].PDF");
                if (utEnd != -1 && (utEnd - utStart) > 5 && str.toUpperCase().substring(utStart + 5, utEnd).length() == 15) {
                    if (str.substring(utStart + 4, utStart + 5).matches("[a-zA-Z]+")) {
                        return "UT" + str.substring(utStart + 5, utEnd);
                    }
                }
            }
            int ggStart = str.indexOf("[GG");
            if (ggStart != -1) {
                int ggEnd = str.toUpperCase().indexOf("].PDF");
                if (ggEnd != -1 && (ggEnd - ggStart) > 4 && NumberUtil.isInteger(str.substring(ggStart + 4, ggEnd))) {
                    int id = Integer.parseInt(str.substring(ggStart + 4, ggEnd));
                    if (id > 900000000 && id < 1000000000 && !str.substring(ggStart + 4).startsWith("0")) {
                        if (str.substring(ggStart + 3, ggStart + 4).matches("[a-zA-Z]+")) {
                            return "GG" + str.substring(ggStart + 4, ggEnd);
                        }
                    }
                }
            }
            int ggsxStart = str.indexOf("[GGSX");
            if (ggsxStart != -1) {
                int ggsxEnd = str.toUpperCase().indexOf("].PDF");
                if (ggsxEnd != -1 && (ggsxEnd - ggsxStart) > 6 && NumberUtil.isInteger(str.substring(ggsxStart + 6, ggsxEnd))) {
                    int id = Integer.parseInt(str.substring(ggsxStart + 6, ggsxEnd));
                    if (id > 900000000 && id < 1000000000 && !str.substring(ggsxStart + 6).startsWith("0")) {
                        if (str.substring(ggsxStart + 5, ggsxStart + 6).matches("[a-zA-Z]+")) {
                            return "GG" + str.substring(ggsxStart + 6, ggsxEnd);
                        }
                    }
                }
            }
            int cbStart = str.indexOf("[CBID");
            if (cbStart != -1) {
                int cbEnd = str.toUpperCase().indexOf("].PDF");
                if (cbEnd != -1 && (cbEnd - cbStart) > 6 && NumberUtil.isInteger(str.substring(cbStart + 6, cbEnd))) {
                    int id = Integer.parseInt(str.substring(cbStart + 6, cbEnd));
                    if (id > 10000000 && id < 999999999 && !str.substring(cbStart + 6).startsWith("0")) {
                        if (str.substring(cbStart + 5, cbStart + 6).matches("[a-zA-Z]+")) {
                            return "CB" + str.substring(cbStart + 6, cbEnd);
                        }
                    }
                }
            }
            int cjStart = str.indexOf("[CJID");
            if (cjStart != -1) {
                int cjEnd = str.toUpperCase().indexOf("].PDF");
                if (cjEnd != -1 && (cjEnd - cjStart) > 6 && NumberUtil.isInteger(str.substring(cjStart + 6, cjEnd))) {
                    int id = Integer.parseInt(str.substring(cjStart + 6, cjEnd));
                    if (id > 10000000 && id < 999999999 && !str.substring(cjStart + 6).startsWith("0")) {
                        if (str.substring(cjStart + 5, cjStart + 6).matches("[a-zA-Z]+")) {
                            return "CJ" + str.substring(cjStart + 6, cjEnd);
                        }
                    }
                }
            }
            int ebStart = str.indexOf("[EBID");
            if (ebStart != -1) {
                int ebEnd = str.toUpperCase().indexOf("].PDF");
                if (ebEnd != -1 && (ebEnd - ebStart) > 6 && NumberUtil.isInteger(str.substring(ebStart + 6, ebEnd))) {
                    int id = Integer.parseInt(str.substring(ebStart + 6, ebEnd));
                    if (id > 10000000 && id < 999999999 && !str.substring(ebStart + 6).startsWith("0")) {
                        if (str.substring(ebStart + 5, ebStart + 6).matches("[a-zA-Z]+")) {
                            return "EB" + str.substring(ebStart + 6, ebEnd);
                        }
                    }
                }
            }
            int ejStart = str.indexOf("[EJID");
            if (ejStart != -1) {
                int ejEnd = str.toUpperCase().indexOf("].PDF");
                if (ejEnd != -1 && (ejEnd - ejStart) > 6 && NumberUtil.isInteger(str.substring(ejStart + 6, ejEnd))) {
                    int id = Integer.parseInt(str.substring(ejStart + 6, ejEnd));
                    if (id > 10000000 && id < 999999999 && !str.substring(ejStart + 6).startsWith("0")) {
                        if (str.substring(ejStart + 5, ejStart + 6).matches("[a-zA-Z]+")) {
                            return "EJ" + str.substring(ejStart + 6, ejEnd);
                        }
                    }
                }
            }
            if (str.length() == 12) {
                int bookEnd = str.indexOf(".PDF");
                if (bookEnd != -1 && NumberUtil.isInteger(str.substring(0, bookEnd))) {
                    int id = Integer.parseInt(str.substring(0, bookEnd));
                    if (id > 10000000 && id < 99999999 && !str.startsWith("0")) {
                        return "EB" + str.substring(0, bookEnd);
                    }
                }
            }
        }
        return "";
    }

    // Summer 1987;50(3):35-6.->1987  1977;7:8-10.->1977  1985 Jun;134(6):64-8.->1985 正则匹配出版年，允许年后有空格，年后有;，或者就四位数字三种形式。匹配到的返回的是4位数字，匹配不到的，返回0。
    public static Integer getPubYear(String str) {
        if (null != str && str.length() > 3) {
            str = str.replace("；", ";").trim();
            if (str.length() == 4 && (str.startsWith("18") || str.startsWith("19") || str.startsWith("20")) && NumberUtil.isInteger(str)) {
                return Integer.parseInt(str);
            }
            Matcher matcher = Pattern.compile("(19[0-9]{2};|19[0-9]{2} |20[0-9]{2};|20[0-9]{2} |18[0-9]{2};|18[0-9]{2} )").matcher(str);
            if (matcher.find()) {
                String year = matcher.group(1).trim().replace(";", "");
                if (year.length() == 4 && NumberUtil.isInteger(year)) {
                    return Integer.parseInt(year);
                }
            }
        }
        return 0;
    }

    public static void putStrSetInt(String str, Integer id, Map<String, Set<Integer>> strSetIntMap) {
        strSetIntMap.compute(str, (k, v) -> {
            if (v == null) {
                v = new ConcurrentHashSet<>();
            }
            v.add(id);
            return v;
        });
    }

    // 对Map的值进行排序，此排序为最大的在前面，输入一个map，再返回一个排序后的新map，String, Double类型
    public static Map<String, Double> mapSortValue(Map<String, Double> mapName) {
        Map<String, Double> result = new LinkedHashMap<>();
        mapName.entrySet().stream()
                .sorted(Map.Entry.<String, Double>comparingByValue()
                        .reversed()).forEachOrdered(e -> result.put(e.getKey(), e.getValue()));
        return result;
    }

    // 对Map的值进行排序，此排序为最大的在前面，输入一个map，再返回一个排序后的新map，String, Double类型
    public static Map<Integer, Double> intMapSortValue(Map<Integer, Double> mapName) {
        Map<Integer, Double> result = new LinkedHashMap<>();
        mapName.entrySet().stream()
                .sorted(Map.Entry.<Integer, Double>comparingByValue()
                        .reversed()).forEachOrdered(e -> result.put(e.getKey(), e.getValue()));
        return result;
    }

    // 对Map的值进行排序，此排序为最大的在前面，输入一个map，再返回一个排序后的新map，Integer, Integer类型，本排序增加了过滤功能，可以设置要前多少（len）个。
    public static Map<Integer, Double> mapSortValueLenDouble(Map<Integer, Double> mapName, Integer len) {
        Map<Integer, Double> result = new LinkedHashMap<>();
        mapName.entrySet().stream()
                .sorted(Map.Entry.<Integer, Double>comparingByValue()
                        .reversed()).limit(len).forEachOrdered(e -> result.put(e.getKey(), e.getValue()));
        return result;
    }

    // 对Map的值进行排序，此排序为最大的在前面，输入一个map，再返回一个排序后的新map，String, Double类型
    public static Map<List<String>, Double> listMapSortValue(Map<List<String>, Double> mapName) {
        Map<List<String>, Double> result = new LinkedHashMap<>();
        mapName.entrySet().stream()
                .sorted(Map.Entry.<List<String>, Double>comparingByValue()
                        .reversed()).forEachOrdered(e -> result.put(e.getKey(), e.getValue()));
        return result;
    }

    public static void putIntSetIntMap(Map<Integer, Set<Integer>> finishAidListSetMap, int max, int min) {
        finishAidListSetMap.compute(max, (k, v) -> {
            if (v == null) {
                v = new ConcurrentHashSet<>();
            }
            v.add(min);
            return v;
        });
    }

    public static void putListStrListInt(List<String> list, Integer id, Map<List<String>, Set<Integer>> listStrListIntMap) {
        listStrListIntMap.compute(list, (k, v) -> {
            if (v == null) {
                v = new ConcurrentHashSet<>();
            }
            v.add(id);
            return v;
        });
    }

    // 取小数点后4位
    public static double takeFourDigits(double d) {
        if (Double.isNaN(d)) {
            return 0.0;
        }
        BigDecimal bd = new BigDecimal(d);
        BigDecimal bd2 = bd.setScale(4, RoundingMode.HALF_UP);
        return Double.parseDouble(bd2.toString());
    }

    // 插入这类的Map：主键为String，值为List，且为String类型。
    public static void putStrListStr(Map<String, List<String>> strListStrMap, String name, String list) {
        strListStrMap.compute(name, (k, v) -> {
            if (v == null) {
                v = Collections.synchronizedList(new ArrayList<String>());
            }
            v.add(list);
            return v;
        });
    }

    // 判断是否包含中文汉字
    public static boolean isContainChinese(String str) {
        Pattern p = Pattern.compile("[\u4e00-\u9fa5]");
        Matcher m = p.matcher(str);
        return m.find();
    }

    // 判断是否为六大刊，若是返回1，此方法与期刊唯一JID不同，只针对期刊名称进行判定。
    public static int is6DaJournals(String nlmId, String nlmAbb, String wosFull, String wosJ9) {
        nlmId = null == nlmId ? "" : nlmId.trim().toUpperCase();
        nlmAbb = null == nlmAbb ? "" : nlmAbb.trim().toUpperCase();
        wosFull = null == wosFull ? "" : wosFull.trim().toUpperCase();
        wosJ9 = null == wosJ9 ? "" : wosJ9.trim().toUpperCase();
        if ("0255562".equals(nlmId) || "2985213R".equals(nlmId) || "7501160".equals(nlmId) || "0413066".equals(nlmId) || "0410462".equals(nlmId) || "0404511".equals(nlmId)) {
            return 1;
        } else if ("N ENGL J MED".equals(nlmAbb) || "LANCET".equals(nlmAbb) || "JAMA".equals(nlmAbb) || "CELL".equals(nlmAbb) || "NATURE".equals(nlmAbb) || "SCIENCE".equals(nlmAbb)) {
            return 1;
        } else if ("NEW ENGLAND JOURNAL OF MEDICINE".equals(wosFull) || "LANCET".equals(wosFull) || "JAMA-JOURNAL OF THE AMERICAN MEDICAL ASSOCIATION".equals(wosFull) || "CELL".equals(wosFull) || "NATURE".equals(wosFull) || "SCIENCE".equals(wosFull)) {
            return 1;
        } else if ("NEW ENGL J MED".equals(wosJ9) || "LANCET".equals(wosJ9) || "JAMA-J AM MED ASSOC".equals(wosJ9) || "CELL".equals(wosJ9) || "NATURE".equals(wosJ9) || "SCIENCE".equals(wosJ9)) {
            return 1;
        }
        return 0;
    }


    //获取两字符串的相似度_1
    public static double getSimilarityRatio(String str, String target) {
        int max = Math.max(str.length(), target.length());
        return 1 - (double) compare(str, target) / max;
    }

    //获取两字符串的相似度_2 两字符串相似度计算算法 Levenshtein Distance编辑距离算法
    private static int compare(String str, String target) {
        int d[][];
        int n = str.length();
        int m = target.length();
        int i;
        int j;
        char ch1;
        char ch2;
        int temp;
        if (n == 0) {
            return m;
        }
        if (m == 0) {
            return n;
        }
        d = new int[n + 1][m + 1];
        for (i = 0; i <= n; i++) {
            d[i][0] = i;
        }
        for (j = 0; j <= m; j++) {
            d[0][j] = j;
        }
        for (i = 1; i <= n; i++) {
            ch1 = str.charAt(i - 1);
            for (j = 1; j <= m; j++) {
                ch2 = target.charAt(j - 1);
                if (ch1 == ch2 || ch1 == ch2 + 32 || ch1 + 32 == ch2) {
                    temp = 0;
                } else {
                    temp = 1;
                }
                d[i][j] = min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + temp);
            }
        }
        return d[n][m];
    }

    //获取两字符串的相似度_3
    private static int min(int one, int two, int three) {
        return (one = Math.min(one, two)) < three ? one : three;
    }


    // 判断是否符合邮箱的格式
    public static Boolean fuHeiMail(String mail) {
        if (null != mail && mail.length() > 5 && mail.contains("@") && !mail.startsWith("@") && !mail.endsWith("@") && mail.contains(".")) {
            List<String> list = Arrays.asList(mail.split("@"));
            return list.size() == 2 && list.get(0).length() > 1 && list.get(1).contains(".") && list.get(1).length() > 3 && mail.substring(mail.lastIndexOf(".")).length() < 6;
        }
        return false;
    }

    public static void putIntStrIntSum(Integer len, String join, Map<Integer, Map<String, Integer>> intStrIntMap) {
        intStrIntMap.compute(len, (k, v) -> {
            if (v == null) {
                v = new ConcurrentHashMap<>();
            }
            v.compute(join, (key, value) -> value != null ? (value + 1) : 1);
            return v;
        });
    }


}
